import React, { Component } from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';

import Touch from 'modules/Touch/core';

import S from './index.m.less';

const requestAnimationFrame = window.requestAnimationFrame || ((fn) => window.setTimeout(fn, 16));

const getMatchedIndex = (items = [], tab) => items.findIndex(({ key }) => key == tab);

const transClassName = (className = '') => className.replace(/-+(.)?/g, (m, c) => (c ? c.toUpperCase() : ''));

// 展示模式
const tabsMode = {
    // 默认下划线
    underline: 'underline',
    // 边框
    border: 'border',
    // 滑块
    slider: 'slider',
};

export default class ScrollTabs extends Component {
    static propTypes = {
        className: PropTypes.string, // 组件样式
        sliderClassName: PropTypes.string, // 导航条样式
        itemClassName: PropTypes.string, // item默认样式
        activeClassName: PropTypes.string, // item选中样式
        items: PropTypes.arrayOf(
            PropTypes.shape({
                title: PropTypes.string.isRequired, // 显示名称
                style: PropTypes.object,
                className: PropTypes.string,
                activeClassName: PropTypes.string, // 选中状态样式
                onClick: PropTypes.func, // 点击事件
                key: PropTypes.any, // 对应 key 值
                renderItem: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
            })
        ).isRequired,
        currentTab: PropTypes.any, // 当前选中的tab
        onItemClick: PropTypes.func, // tab 点击回调
        renderItem: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), // 定制tab渲染
        autoFocus: PropTypes.bool, // 自动定位聚焦
        usePageTouchX: PropTypes.bool,
        useTouchX: PropTypes.bool, // 手势切换tab标签
        spaceBetween: PropTypes.bool,
        // 指定ScrollTabs内元素的左右边距
        space: PropTypes.shape({
            padding: PropTypes.string, // 左右边距
            between: PropTypes.string, // 中间边距
        }),
        mode: PropTypes.oneOf(Object.values(tabsMode)),
    };

    static defaultProps = {
        className: '',
        itemClassName: '',
        activeClassName: '',
        currentTab: undefined,
        onItemClick: undefined,
        renderItem: undefined,
        autoFocus: true,
        usePageTouchX: false,
        useTouchX: false,
        spaceBetween: false,
        space: {
            padding: '0',
            between: '0',
        },
        mode: tabsMode.underline,
    };

    static TM = tabsMode;

    sliderRef = React.createRef();

    containerRef = React.createRef();

    rootRef = React.createRef();

    itemRefs = [];

    get $root() {
        return this.rootRef.current;
    }

    get $slider() {
        return this.sliderRef.current;
    }

    get $container() {
        return this.containerRef.current;
    }

    get selectedIndex() {
        return this.state.selectedIndex;
    }

    get $selectItem() {
        return this.getItemNode(this.selectedIndex);
    }

    constructor(props) {
        super(props);
        const { items = [], currentTab } = props;
        this.state = {
            selectedIndex: getMatchedIndex(items, currentTab),
        };
    }

    componentDidMount() {
        const { useTouchX, usePageTouchX, autoFocus, items = [] } = this.props;

        (useTouchX || usePageTouchX) && this.appendTouchX(usePageTouchX);

        if (autoFocus && this.selectedIndex > 0 && this.selectedIndex < items.length) {
            requestAnimationFrame(() => {
                this.scrollToFocus(this.getItemNode(this.selectedIndex));
            });
        }

        this.setSelectedPosition(this.selectedIndex);
    }

    UNSAFE_componentWillReceiveProps({ items = [], currentTab } = {}) {
        const selectedIndex = getMatchedIndex(items, currentTab);
        if (selectedIndex !== this.selectedIndex) {
            this.setState(
                {
                    selectedIndex,
                },
                () => {
                    this.setSelectedPosition(selectedIndex);
                    requestAnimationFrame(() => {
                        this.scrollToFocus(this.getItemNode(this.selectedIndex));
                    });
                }
            );
        }
    }

    componentWillUnmount() {
        this._destroyed = true;
        this.touchEvent && this.touchEvent.destroy();
    }

    getItemNode = (index) => {
        if (index < 0 || index >= this.props.items.length) return undefined;
        return this.itemRefs[index];
    };

    setSelectedIndex = (index) => {
        const { items = [] } = this.props;
        if (index !== this.selectedIndex && index >= 0 && index < items.length) {
            const $target = this.getItemNode(index);
            this.handleItemClick(items[index], index, $target);
        }
    };

    appendTouchX = (usePageTouchX) => {
        let $target = $(this.$container).closest('#Page');

        if (!usePageTouchX) {
            $target = $target.find('.tabTouchRootDom');
        }
        const [$root] = $target;

        if (!$root) return;

        const $slider = this.$slider;
        let canSlide = true;
        let scaleW;

        // 实例化Touch
        this.touchEvent = new Touch($root, {
            preventDefault: false,
            ingoreTouchElement: ['wpt-swipe', 'slick-slider', 'wpt-scroll-tabs'],
        })
            .on('start', () => {
                if (!this.$selectItem) {
                    canSlide = false;
                    return;
                }
                if (this.selectedIndex !== 0) {
                    $root.classList.add('wpt-swipe');
                }
                canSlide = true;
                if ($slider) {
                    $slider.style.cssText += `;transition: none;`;
                    const sWidth = this.$selectItem.clientWidth;
                    scaleW = sWidth / $root.clientWidth;

                    this.$selectItem.classList.add(S.hideSlide);
                }
            })
            .on('change', ({ x }, state, e) => {
                const { selectedIndex } = this;
                if (
                    !canSlide ||
                    state.direction !== 'lr' ||
                    !$slider ||
                    (selectedIndex === 0 && x >= 0) || // 为0时右滑不触发， class_active存在不触发
                    (selectedIndex === this.props.items.length - 1 && x < 0) // 左滑到头为length-1时也不触发
                ) {
                    canSlide = false;
                    return;
                }
                canSlide = true;
                e.preventDefault();
                this.setSelectedPosition(this.selectedIndex, -x * scaleW);
            })
            .on('end', ({ x }, done) => {
                done();
                if (!canSlide) {
                    return;
                }
                $root.classList.remove('wpt-swipe');

                const { selectedIndex } = this;

                // 首先我再一个位置上，然后动画，然后设定left
                // 当然也有可能
                const { items } = this.props;
                if (Math.abs(x) > 80) {
                    const targetIndex = x > 0 ? selectedIndex - 1 : selectedIndex + 1;
                    const $target = this.getItemNode(targetIndex);
                    $target.classList.remove(S.hideSlide);
                    this.handleItemClick(items[targetIndex], targetIndex, $target);
                    // 获取他的位置 index
                    // 目标元素的左边距
                } else {
                    const index = this.selectedIndex;
                    this.handleItemClick(items[index], index);
                }
            });
    };

    getPerWidth(index) {
        if (this[`_oneWidth${index}`]) {
            return this[`_oneWidth${index}`];
        }

        if (!this.itemRefs[index]) {
            return 0;
        }

        // left是中心点，left是50% 宽度的 50%
        this[`_oneWidth${index}`] = this.itemRefs[index].offsetLeft + this.itemRefs[index].clientWidth / 2;
        return this[`_oneWidth${index}`];
    }

    /**
     * 设置底部选中条的位置
     * @memberof ScrollTabs
     */
    setSelectedPosition = (targetIndex, moveX = 0, animation = false) => {
        const { $slider } = this;
        if (!$slider) return;

        if (animation) {
            $slider.style.cssText += `;transition: transform 0.2s;`;
            $slider.clientWidth;
        }
        $slider.style.cssText += `;transform: translateX(${this.getPerWidth(targetIndex) + moveX}px);`;
    };

    scrollToFocus = ($item, time = 200, minStep = 3) => {
        // dom实例不存在
        if (!this.$container || !$item) return;
        const { offsetWidth: w, offsetLeft } = this.$root;
        const { scrollWidth: sw, scrollLeft: sl, offsetWidth: cw } = this.$container;
        // 内容不产生scroll
        if (cw >= sw) return;

        const { offsetLeft: left, offsetWidth: width } = $item;
        const half = (w - width - offsetLeft) / 2;
        // 滚动
        const targetLeft = Math.min(Math.max(left - half, 0), sw - cw);
        // 200毫秒完成动画
        const diff = (targetLeft - sl) / (time / 13);
        if (diff === 0) return;
        const per = diff < 0 ? Math.min(-minStep, diff) : Math.max(minStep, diff);
        let currentLeft = sl;
        const scroll = () => {
            if (this._destroyed) return;
            currentLeft += per;
            if ((diff < 0 && currentLeft <= targetLeft) || (diff > 0 && currentLeft >= targetLeft)) {
                this.$container.scrollLeft = targetLeft;
                return;
            }
            this.$container.scrollLeft = currentLeft;
            requestAnimationFrame(scroll);
        };
        scroll();
    };

    handleItemClick = (item = {}, selectedIndex, $target) => {
        this.setSelectedPosition(selectedIndex, 0, true);
        if (selectedIndex === this.state.selectedIndex) return;
        const { onItemClick } = this.props;
        this.setState(
            {
                selectedIndex,
            },
            () => {
                this.scrollToFocus($target);
                onItemClick && onItemClick(item, selectedIndex);
                item.onClick && item.onClick();
            }
        );
    };

    renderItem = (item = {}, i) => {
        const { itemClassName, activeClassName, mode } = this.props;

        const renderItem = item.renderItem || this.props.renderItem;

        const { style, className, key = i } = item;

        const selected = this.state.selectedIndex === i;
        let title = item.title || item.name;

        if (typeof renderItem === 'function') {
            title = renderItem(item, selected, i);
        } else if (renderItem) {
            const ItemNode = renderItem;
            title = <ItemNode item={item} selected={selected} index={i} />;
        }
        return (
            <div
                key={key}
                className={cx(
                    S.tabItem,
                    S[transClassName(`${mode}-item`)],
                    itemClassName,
                    className,
                    selected && S.selected,
                    selected && activeClassName,
                    selected && item.activeClassName
                )}
                ref={(n) => {
                    this.itemRefs[i] = n;
                }}
                style={{
                    marginLeft: this.props.space.between,
                    ...style,
                }}
                onClick={(e) => {
                    this.handleItemClick(item, i, e.currentTarget);
                }}
            >
                {title}
            </div>
        );
    };

    renderSlider() {
        const { mode, sliderClassName } = this.props;

        return (
            <div
                className={cx(S.swipeSlider, S[transClassName(`slide-${mode}`)], sliderClassName)}
                ref={this.sliderRef}
            />
        );
    }

    render() {
        const { className, items = [], spaceBetween } = this.props;
        if (!items || !items.length) {
            return null;
        }
        return (
            <div className={cx('wpt-scroll-tabs', S.scrollTabs, className)} ref={this.rootRef}>
                <div className={cx(S.tabsInner, spaceBetween && S.spaceBetween)} ref={this.containerRef}>
                    <div style={{ paddingRight: this.props.space.padding }}>
                        {items.map(this.renderItem)}
                        {this.renderSlider()}
                    </div>
                </div>
            </div>
        );
    }
}
